package biolabs.core

import java.util.concurrent.Callable

/**
 * Clock is a source of ticks in simulation time. Ticks are not
 * necessarily emitted in regular intervals of simulation time.
 * Current simulation time is provided with every tick.
 */
trait Clock {
	def addClockListener(l: Clock.ClockListener)
	def removeClockListener(l: Clock.ClockListener)
}

object Clock {

	trait ClockListener {
		def tick(time: Long)
		def terminated()
	}

	/** Implicit conversion of Experiment to Clock.
	  *
	  * If Experiment was written in Scala, it would be sufficient
	  * to mark its `asReadLockedClock()` method implicit.
	  */
	implicit def experiment2Clock(e: Experiment): Clock =
		e.asReadLockedClock()

	// XXX the following experiment-related case classes should rather be
	// placed in the Experiment's source file, but that is a Java file
	// and they are really concisely implemented as Scala case classes.

	/**
	 * Makes a clock listener look like an experiment listener.
	 * When registered with an experiment, a ticks is emitted
	 * every time the experiment is stopped.
	 */
	case class ClockToExperimentListener(l: ClockListener) extends ExperimentListener {
		override def experimentClosed(e: Experiment) { l.terminated() }
		override def experimentStarted(e: Experiment) {}
		override def experimentStopped(e: Experiment) {	l.tick(e.getStep()) }
	}

	/**
	 * Makes a clock listener look like an experiment listener.
	 * When registered with an experiment, a ticks is emitted
	 * every time the experiment is stopped. The tick is executed
	 * under experiment's read lock.
	 */
	case class ClockToReadLockedExperimentListener(l: ClockListener) extends ExperimentListener {
		override def experimentClosed(e: Experiment) { l.terminated() }
		override def experimentStarted(e: Experiment) {}
		override def experimentStopped(e: Experiment) {
			e executeUnderReadLock new Callable[Void] {
				override def call(): Void = {
					l.tick(e.getStep())
					null
				}
			}
		}
	}

	/**
	 * Makes a clock listener look like an experiment listener.
	 * When registered with an experiment, a ticks is emitted
	 * every time the experiment is stopped. The tick is executed
	 * under experiment's write lock.
	 */
	case class ClockToWriteLockedExperimentListener(l: ClockListener) extends ExperimentListener {
		override def experimentClosed(e: Experiment) { l.terminated() }
		override def experimentStarted(e: Experiment) {}
		override def experimentStopped(e: Experiment) {
			e executeUnderWriteLock new Callable[Void] {
				override def call(): Void = {
					l.tick(e.getStep())
					null
				}
			}
		}
	}

}